//
// Created by user on 20-3-16.
//

#include "Memory.h"
#include "Malloc.h"

int ZONE_DMA_INDEX = 0;             // 0-16MB预留给I/O的，这里也是表示没有映射
int ZONE_NORMAL_INDEX = 0;          // 低于1GB的内存没有映射
int ZONE_UNMAPED_INDEX = 0;         // 高于1GB的内存没有映射

// 全局CR3
unsigned long *globalCR3 = NULL;

void initMemory() {
    // 循环控制变量
    int i, j;

    // 总共内存数量
    unsigned long TotalMem = 0;

    // E820内存结构指针
    E820 *e = NULL;

    // 1. 提示一下
//    printfColor(BLUE, BLACK,
//                "Display Physics Address MAP, Type(1: RAM,2: ROM or Reserved,3: ACPI Reclaim Memory, 4: ACPI NVS Memory, Others: Undefine)\n");

    // 2. 把在 Loader 程序用 int 15h 中断读出来的数据的地址放到 p 里面
    e = (E820 *) 0xffff800000007e00;

    // 3. 把数据赋值给指定变量
    for (i = 0; i < 32; i++) {
//        printfColor(ORANGE, BLACK, "Address: %x\tLength: %x\tType: %x\n", e->address, e->length, e->type);
        unsigned long tmp = 0;
        if (e->type == 1) {
            TotalMem += e->length;
        }

        memoryManagementStruct.e820[i].address = e->address;

        memoryManagementStruct.e820[i].length = e->length;

        memoryManagementStruct.e820[i].type = e->type;

        memoryManagementStruct.e820Length = (unsigned long) i;

        e++;

        // 判断物理段是否无效
        if (e->type > 4 || e->length == 0 || e->type < 1)
            break;
    }

//    printfColor(GREEN, BLACK, "OS Can Used Total RAM: %ld\n", TotalMem);

    TotalMem = 0;

    // 4. 统计总共可用 2M 大小物理页的总数
    for (i = 0; i <= memoryManagementStruct.e820Length; i++) {

        unsigned long start, end;

        if (memoryManagementStruct.e820[i].type != 1)
            continue;

        // 获取到对齐好的地址
        start = PAGE_2M_ALIGN(memoryManagementStruct.e820[i].address);

        // 获取到结束的地址
        end = ((memoryManagementStruct.e820[i].address + memoryManagementStruct.e820[i].length) >> PAGE_2M_SHIFT)
                << PAGE_2M_SHIFT;

        if (end <= start)
            continue;

        TotalMem += (end - start) >> PAGE_2M_SHIFT;

    }

//    printfColor(ORANGE, BLACK, "OS Can Used Total 2M PAGEs: %x = %d\n", TotalMem, TotalMem);

    // 5. 将内存结构体数组中的最后一个拿出来，求出物理地址空间的结束地址
    TotalMem = memoryManagementStruct.e820[memoryManagementStruct.e820Length].address +
               memoryManagementStruct.e820[memoryManagementStruct.e820Length].length;

    // 6. 初始化位图，这个是一个位代表这一个页面，这样可以通过检索这些位就能知道对应的页面是否已经分配了
    // 1> 在内核程序结束的地方找到一段 4K 对齐的内存，作为起始地址
    memoryManagementStruct.pBitsMap = (unsigned long *) ((memoryManagementStruct.kernelEndBrk + PAGE_4K_SIZE - 1) &
                                                         PAGE_4K_MASK_CODE);

    // 2> 计算出物理内存段可以分为多少页 2M 的页面
    memoryManagementStruct.bitsCount = TotalMem >> PAGE_2M_SHIFT;

    // 3> 求出位图长度
    memoryManagementStruct.bitsLength =
            (((TotalMem >> PAGE_2M_SHIFT) + sizeof(long) * 8 - 1) / 8) & (~(sizeof(long) - 1));

    // 4> 将指定位图置位
    memset(memoryManagementStruct.pBitsMap, 0xFF, memoryManagementStruct.bitsLength);

    // 7. 初始化页数组
    // 1> 位图后面就是页数组的内存
    memoryManagementStruct.pPageArray = (Page *) (
            ((unsigned long) memoryManagementStruct.pBitsMap + memoryManagementStruct.bitsLength + PAGE_4K_SIZE - 1) &
            PAGE_4K_MASK_CODE);

    memoryManagementStruct.pageTotalCount = TotalMem >> PAGE_2M_SHIFT;

    memoryManagementStruct.pageArrayLength =
            ((TotalMem >> PAGE_2M_SHIFT) * sizeof(Page) + sizeof(long) - 1) & (~(sizeof(long) - 1));

    memset(memoryManagementStruct.pPageArray, 0x00, memoryManagementStruct.pageArrayLength);

    // 8. 初始化区域空间数组
    // 1> 页数组后面便是区域空间的内存
    memoryManagementStruct.pZoneArray = (Zone *) (
            ((unsigned long) memoryManagementStruct.pPageArray + memoryManagementStruct.pageArrayLength + PAGE_4K_SIZE -
             1) & PAGE_4K_MASK_CODE);

    memoryManagementStruct.zoneTotalCount = 0;

    memoryManagementStruct.zoneArrayLength = (5 * sizeof(Zone) + sizeof(long) - 1) & (~(sizeof(long) - 1));

    memset(memoryManagementStruct.pZoneArray, 0x00, memoryManagementStruct.zoneArrayLength);

    // 9. 初始化 Zone 和 Page 结构体的属性
    for (i = 0; i <= memoryManagementStruct.e820Length; i++) {

        unsigned long start, end;
        Zone *pZone;
        Page *pPage;

        // 1> 判断是否不是不可用物理内存段
        if (memoryManagementStruct.e820[i].type != 1)
            continue;

        // 2> 获取一个 2M 对齐的起始地址
        start = PAGE_2M_ALIGN(memoryManagementStruct.e820[i].address);

        // 3> 获取一个 2M 对齐的结束地址
        end = ((memoryManagementStruct.e820[i].address + memoryManagementStruct.e820[i].length) >> PAGE_2M_SHIFT)
                << PAGE_2M_SHIFT;

        // 4> 判断是否结束地址大于起始地址，防止有些段不足 2M
        if (end <= start)
            continue;

        // 5> 初始化 zone 结构体
        pZone = memoryManagementStruct.pZoneArray + memoryManagementStruct.zoneTotalCount;

        memoryManagementStruct.zoneTotalCount++;

        pZone->startAddress = start;
        pZone->endAddress = end;
        pZone->length = end - start;

        pZone->pageUsingCount = 0;
        pZone->pageFreeCount = (end - start) >> PAGE_2M_SHIFT;

//        printfColor(INDIGO, BLACK, "pageFreeCount = %d\n", pZone->pageFreeCount);

        pZone->totalPagesLink = 0;

        pZone->attribute = 0;
        pZone->gmd = &memoryManagementStruct;

        pZone->pageArrayLength = (end - start) >> PAGE_2M_SHIFT;
        pZone->pPageArray = memoryManagementStruct.pPageArray + (start >> PAGE_2M_SHIFT);

        // 6> 初始化 page 结构体
        pPage = pZone->pPageArray;

        for (j = 0; j < pZone->pageArrayLength; j++) {

            pPage = pPage + (j == 0 ? 0 : 1);
            pPage->pZone = pZone;
            pPage->physicalAddress = start + PAGE_2M_SIZE * j;
//            printfColor(GREEN, BLACK, "test!!!\n");
//            printfColor(INDIGO, BLACK, "%lx  = %lx\n", p, p->physicalAddress);
            pPage->attribute = 0;

            pPage->referenceCount = 0;

            pPage->createTime = 0;
            // p->physicalAddress >> PAGE_2M_SHIFT 得到的是物理页以 2M 为大小的页面可以划分多少个
            // % 64 得到的是 bitsMap 映射位图对应的位
            // >> 6 其实是计算出以 64 位操作系统来计算
            *(memoryManagementStruct.pBitsMap + ((pPage->physicalAddress >> PAGE_2M_SHIFT) >> 6)) ^=
                    1UL << (pPage->physicalAddress >> PAGE_2M_SHIFT) % 64;
        }

    }

    // 10. 因为 0-2MB 的物理内存包含了很多物理内存段，甚至还有内核程序，所以需要对这些内存进行特殊处理
    memoryManagementStruct.pPageArray->pZone = memoryManagementStruct.pZoneArray;

    memoryManagementStruct.pPageArray->physicalAddress = 0UL;
    setPageAttribute(memoryManagementStruct.pPageArray, PG_PTable_Maped | PG_Kernel_Init | PG_Kernel);
    memoryManagementStruct.pPageArray->referenceCount = 1;
    memoryManagementStruct.pPageArray->createTime = 0;

    memoryManagementStruct.zoneArrayLength =
            (memoryManagementStruct.zoneTotalCount * sizeof(Zone) + sizeof(long) - 1) & (~(sizeof(long) - 1));

    // 11.
//    ZONE_DMA_INDEX = 0;
//    ZONE_NORMAL_INDEX = 0;
//    ZONE_UNMAPED_INDEX = 0;

    // 12. 这里重新打印，已经统计好的内存信息
    for (i = 0; i < memoryManagementStruct.zoneTotalCount; i++) {

        Zone *pZone = memoryManagementStruct.pZoneArray + i;

//        printfColor(ORANGE, BLACK,
//                    "zoneStartAddress: %x, zoneEndAddress: %x, zoneLength: %x, pagesGroup: %x, pagesLength: %x\n",
//                    pZone->startAddress, pZone->endAddress, pZone->length, pZone->pPageArray, pZone->length);

        // 如果是 0x100000000 ，就说明它还有没有并映射，这里记录一下
        if (pZone->startAddress == 0x100000000 && !ZONE_UNMAPED_INDEX)
            ZONE_UNMAPED_INDEX = i;

    }

    // 12. 这里为内存管理结构的结束地址赋值，注意还预留了一段空白的内存来防止越界访问。
    memoryManagementStruct.endOfStruct = ((unsigned long) memoryManagementStruct.pZoneArray +
                                          memoryManagementStruct.zoneArrayLength +
                                          sizeof(long) * 32) & (~(sizeof(long) - 1));

//    printfColor(ORANGE, BLACK,
//                "startCode: %lx, endCode:%lx, endData:%lx, endBrk: %lx, endOfStruct: %lx\n",
//                memoryManagementStruct.kernelStartCode, memoryManagementStruct.kernelEndCode,
//                memoryManagementStruct.kernelEndData, memoryManagementStruct.kernelEndBrk,
//                memoryManagementStruct.endOfStruct);

    // 获取到内核使用的一段内存可分配多少页 2M 的页面
    i = (int) (Virtual_To_Physical(memoryManagementStruct.endOfStruct) >> PAGE_2M_SHIFT);

    // 将内核使用的 2M 页面全部初始化，主要是设置页的属性，还有就是更新页的统计信息
    for (j = 0; j <= i; j++) {

        Page *pTmpPage = memoryManagementStruct.pPageArray + j;
        pageInit(pTmpPage, PG_PTable_Maped | PG_Kernel_Init | PG_Active | PG_Kernel);
        *(memoryManagementStruct.pBitsMap + ((pTmpPage->physicalAddress >> PAGE_2M_SHIFT) >> 6)) |=
                1UL << (pTmpPage->physicalAddress >> PAGE_2M_SHIFT) % 64;
        pTmpPage->pZone->pageUsingCount++;
        pTmpPage->pZone->pageFreeCount--;

    }

    // 初始化全局CR3变量
    globalCR3 = getCR3();

    // 消除一致性页表映射，将页目录的前10个页表项清零
//    for (i = 0; i < 10; i++)
//        *(Physical_To_Virtual(globalCR3) + i) = 0UL;

    // 刷新 TLB
    flushTLB();

}


void pageTableInit() {

    unsigned long i, j;
    unsigned long *tmpAddress = NULL;

    globalCR3 = getCR3();

    // 1. 获取到 CR3 存储的地址的线性地址，0xfffUL是屏蔽掉标志位，正好是12位
    tmpAddress = (unsigned long *) (((unsigned long) Physical_To_Virtual((unsigned long) globalCR3 & (~0xfffUL))) +
                                    8 * 256);

    printfColor(YELLOW, BLACK, "->1: %lx, %lx\t\t\n", (unsigned long) tmpAddress, *tmpAddress);

    tmpAddress = Physical_To_Virtual(*tmpAddress & (~0xfffUL));

    printfColor(YELLOW, BLACK, "->2: %lx, %lx\t\t\n", (unsigned long) tmpAddress, *tmpAddress);

    tmpAddress = Physical_To_Virtual(*tmpAddress & (~0xfffUL));

    printfColor(YELLOW, BLACK, "->3: %lx, %lx\t\t\n", (unsigned long) tmpAddress, *tmpAddress);

    // 2. 通过循环遍历内存区域空间
    for (i = 0; i < memoryManagementStruct.zoneTotalCount; i++) {

        Zone *pZone = memoryManagementStruct.pZoneArray + i;
        Page *pPage = pZone->pPageArray;

        // 1> 判断当前 i 是否与 ZONE_UNMAPED_INDEX 一致，也就是说不遍历 ZONE_UNMAPED_INDEX 区域
        if (ZONE_UNMAPED_INDEX && i == ZONE_UNMAPED_INDEX)
            break;

        // 2> 遍历区域里面的物理页面
        for (j = 0; j < pZone->pageArrayLength; j++, pPage++) {

            // 统一说明以下固定的代码：0x1ff 这个数值是由于 PML4、PDPT、PDT和PT，这些都只占线性地址的9位
            // * 8 是因为每个地址的长度是 8
            // A. 计算出 PML4 的地址
            tmpAddress = (unsigned long *) (
                    ((unsigned long) Physical_To_Virtual((unsigned long) globalCR3 & (~0xfffUL))) +
                    (((unsigned long) Physical_To_Virtual(pPage->physicalAddress) >> PAGE_GDT_SHIFT) & 0x1ff) * 8);

            // B. 判断计算出来的 PML4 的页表项是否为0，如果为0说明这它还没有进行次级页表映射
            if (*tmpAddress == 0) {
                unsigned long *virtual = kmalloc(PAGE_4K_SIZE, 0);
                set_mpl4t(tmpAddress, mk_mpl4t(Virtual_To_Physical(virtual), PAGE_KERNEL_GDT));
            }

            // C. 计算出 PDPT 的地址
            tmpAddress = (unsigned long *) ((unsigned long) Physical_To_Virtual(*tmpAddress & (~0xfffUL)) +
                                            (((unsigned long) Physical_To_Virtual(pPage->physicalAddress)
                                                    >> PAGE_1G_SHIFT) & 0x1ff) * 8);

            // D. 判断计算出来的 PDPT 的页表项是否为0，如果为0说明这它还没有进行次级页表映射
            if (*tmpAddress == 0) {
                unsigned long *virtual = kmalloc(PAGE_4K_SIZE, 0);
                set_pdpt(tmpAddress, mk_pdpt(Virtual_To_Physical(virtual), PAGE_KERNEL_Dir));
            }

            // E. 计算出 PDT 的地址
            tmpAddress = (unsigned long *) ((unsigned long) Physical_To_Virtual(*tmpAddress & (~0xfffUL)) +
                                            (((unsigned long) Physical_To_Virtual(pPage->physicalAddress)
                                                    >> PAGE_2M_SHIFT) & 0x1ff) * 8);

            // F. 直接设置页表
            set_pdt(tmpAddress, mk_pdt(pPage->physicalAddress, PAGE_KERNEL_Page));

//            if (j % 50 == 0)
//                printfColor(GREEN, BLACK, "@: %lx, %lx\t\n", (unsigned long) tmpAddress, *tmpAddress);
        }
    }

    // 3. 刷新CR3，使刚才配置的页表项生效
    flushTLB();

}


unsigned long *getCR3() {

    unsigned long *tmp;

    __asm__ __volatile__    (
    "movq	%%cr3,	%0	\n\t"
    :"=r"(tmp)
    :
    :"memory"
    );

    return tmp;
}

unsigned long pageInit(Page *page, unsigned long flags) {

    // 1. 设置页面的属性
    page->attribute |= flags;

    // 2. 判断页面的引用计数或者属性中有被共享
    if (!page->referenceCount || (page->attribute & PG_Shared)) {
        // 引用计数加一
        page->referenceCount++;
        // 链接计数加一
        page->pZone->totalPagesLink++;
    }

    return 1;

}

unsigned long pageClean(Page *pPage) {

    // 1. 更新页面的引用计数
    pPage->referenceCount--;

    // 2. 更新区域中物理页被引用计数
    pPage->pZone->totalPagesLink--;

    // 3. 判断页面的引用计数是否为 0
    if (!pPage->referenceCount) {
        pPage->attribute &= PG_PTable_Maped;
    }

    return 1;

}

unsigned long getPageAttribute(Page *pPage) {

    if (pPage == NULL) {

        printfColor(RED, BLACK, "get_page_attribute() ERROR: page == NULL\n");
        return 0;

    } else
        return pPage->attribute;

}

unsigned long setPageAttribute(Page *pPage, unsigned long flags) {

    if (pPage == NULL) {

        printfColor(RED, BLACK, "set_page_attribute() ERROR: page == NULL\n");
        return 0;

    } else {

        pPage->attribute = flags;
        return 1;

    }

}

Page *allocPages(int zoneSelect, int number, unsigned long pageFlags) {

    int i;
    unsigned long page = 0;
    unsigned long attribute = 0;

    int zoneStart = 0;
    int zoneEnd = 0;

    if (number >= 64 || number <= 0) {

        printfColor(RED, BLACK, "alloc_pages() ERROR: number is invalid\n");
        return NULL;

    }

    // 1. 判断区域选择在哪个
    switch (zoneSelect) {

        // 判断是否要在 0-16M 分配物理内存页
        case ZONE_DMA:
            zoneStart = 0;
            zoneEnd = ZONE_DMA_INDEX;
            attribute = PG_PTable_Maped;
            break;

            // 判断是否要在 1G 内存里面分配物理内存页
        case ZONE_NORMAL:
            zoneStart = ZONE_DMA_INDEX;
            zoneEnd = ZONE_NORMAL_INDEX;
            attribute = PG_PTable_Maped;
            break;

            // 判断是否要在 1G 以外分配物理内存页
        case ZONE_UNMAPED:
            zoneStart = ZONE_UNMAPED_INDEX;
            zoneEnd = (int) (memoryManagementStruct.zoneTotalCount - 1);
            attribute = 0;
            break;

            // 如果都不是，就报错
        default:
            printfColor(RED, BLACK, "allocPages error zone select index\n");
            return NULL;

    }

    // 2. 开始检索这个区域是否有可以用的物理内存页
    for (i = zoneStart; i <= zoneEnd; i++) {

        Zone *pZone;
        unsigned long j;
        unsigned long start, end, length;
        unsigned long tmp;

        // 1> 判断这个区域可用物理页面是否小于要分配的数量,如果不行，就换下一个
        if ((memoryManagementStruct.pZoneArray + i)->pageFreeCount < number) {
            continue;
        }

        // 2> 获取到这个区域空间的一些信息
        pZone = memoryManagementStruct.pZoneArray + i;
        start = pZone->startAddress >> PAGE_2M_SHIFT;
        end = pZone->endAddress >> PAGE_2M_SHIFT;
        length = pZone->length >> PAGE_2M_SHIFT;

        // 这里是获取到起始页的BIT映射位图最大检索次数，说白了就是获取到与对齐位置的差距有多大，UNSIGNED LONG的宽度是64
        tmp = 64 - start % 64;

        // 开启检索，j += j % 64 ? tmp : 64 这个代码的意思是如果整除的话，就补齐tmp的位置，否则就按64的步长增加
        for (j = start; j <= end; j += j % 64 ? tmp : 64) {

            // 获取到指定位的BIT位图
            unsigned long *p = memoryManagementStruct.pBitsMap + (j >> 6);
            unsigned long shift = j % 64;
            unsigned long k = 0;

            unsigned long num = (1UL << number) - 1;

            for (k = shift; k < 64; k++) {

                if (!((k ? ((*p >> k) | (*(p + 1) << (64 - k))) : *p) & (num))) {

                    unsigned long l;
                    page = j + k - shift;

                    for (l = 0; l < number; l++) {

                        Page *pPage = memoryManagementStruct.pPageArray + page + l;
                        *(memoryManagementStruct.pBitsMap + ((pPage->physicalAddress >> PAGE_2M_SHIFT) >> 6)) |=
                                1UL << (pPage->physicalAddress >> PAGE_2M_SHIFT) % 64;
                        pZone->pageUsingCount++;
                        pZone->pageFreeCount--;

                    }

                    goto findFreePages;

                }
            }


        }

    }

    return NULL;

    findFreePages:
    return (memoryManagementStruct.pPageArray + page);
}


void freePages(Page *pPage, int number) {

    // 1. 判断页面指针是否为空
    if (pPage == NULL) {

        printfColor(RED, BLACK, "free_pages() ERROR: page is invalid\n");
        return;

    }

    // 2. 判断要释放的页面数量是否在 1-64 之间
    if (number >= 64 || number <= 0) {

        printfColor(RED, BLACK, "free_pages() ERROR: number is invalid\n");
        return;

    }

    // 3. 开始释放
    for (int i = 0; i < number; i++, pPage++) {

        // 1> 复位颜色映射位图
        *(memoryManagementStruct.pBitsMap + ((pPage->physicalAddress >> PAGE_2M_SHIFT) >> 6)) &= ~(1UL
                << (pPage->physicalAddress >> PAGE_2M_SHIFT) % 64);

        // 2> 更新区域统计信息
        pPage->pZone->pageUsingCount--;
        pPage->pZone->pageFreeCount++;

        // 3> 清除页面属性
        pPage->attribute = 0;

    }

}